<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Canvas Text Rendering Character by Character</title>
    <style>
        canvas {
            border: 1px solid #000;
        }

         /* 隐藏输入框，但保持其可接受输入 */
    #hidden-input {
        position: absolute;
        opacity: 0;
        pointer-events: none;
    }
    </style>



</head>
<body>
    <canvas id="canvas" width="800" height="300"></canvas>
     <!-- 看不见的输入框 -->
     <input type="text" id="hidden-input">
     <div id="editable-div" contenteditable="true" style="width: 10; height: 10; overflow: hidden;"></div>

    <script>
        
       document.addEventListener("DOMContentLoaded", function() {
       
        const hiddenInput = document.getElementById('hidden-input');

        const canvas = document.getElementById('canvas');
        const ctx = canvas.getContext('2d');
        const textData = {
            text: "您好中国！",
            styles: [
                { start: 0, end: 2, font: "30px 黑体", color: "red" },
                { start: 2, end: 6, font: "45px 楷体", color: "green" }
            ]
        };

        let isSelecting = false;
        let selectionStart = null;
        let selectionEnd = null;
        const charWidths = [];
        let lineHeight = 0;        // 添加行高变量
        let cursorPosition = null; // 光标位置         
        let cursorVisible = false; // 控制光标显示的状态
        let hasSelection = false;  // 添加一个变量来跟踪选择状态
        let isComposing = false;

  

        
                


       
        const editableDiv = document.getElementById('editable-div');
        let initialText = "";      // 记录开始输入时的文本
        let initialSelectionStart = 0; // 记录开始输入时的光标或选区开始位置
        let initialSelectionEnd = 0;   // 记录开始输入时的光标或选区结束位置
        let initialTextLength = 0;
		let initialCursorPosition = 0;
        
        editableDiv.style.width = '1px';
        editableDiv.style.height = '1px';
        editableDiv.style.opacity = 0;


        canvas.addEventListener('click', function() {
            editableDiv.focus();
        });

        editableDiv.addEventListener('compositionend', function(e) {
            console.log(e.data); // 打印输入的文字
            // 更新canvas的文本内容
            isComposing = false;
                // // e.data 包含完成输入的复合字符
                // // 在这里插入字符到文本中
                const beforeCursor = textData.text.slice(0, cursorPosition);
                const afterCursor = textData.text.slice(cursorPosition);
                textData.text = beforeCursor + e.data + afterCursor;
                
                cursorPosition += e.data.length; // 更新光标位置，这里假设插入文本的长度就是e.data的长度
                console.log('update	:', e.data);

                renderText(); // 重新渲染文本
                // const finalText = e.data; // 最终输入的文本
                // const deleteLength = initialSelectionEnd - initialSelectionStart; // 如果有选区，计算其长度

                // // 如果有需要删除的文本，先处理删除
                // if (deleteLength > 0) {
                //     updateStylesForDelete(initialSelectionStart, deleteLength);
                //     textData.text = textData.text.slice(0, initialSelectionStart) + textData.text.slice(initialSelectionEnd);
                // }
                // else{
 				// 	// 然后处理文本插入
               	// 	 updateStylesForInsert(initialSelectionStart, finalText.length);
               	// 	 textData.text = textData.text.slice(0, initialSelectionStart) + finalText + textData.text.slice(initialSelectionStart);

                //     // 调用insertTextAtPosition插入文本
                //     insertTextAtPosition(finalText, cursorPosition);	
                //     console.log(cursorPosition);	


                // }

               
                renderText(); // 重新渲染文本和样式
        });

        // 确保editableDiv可以接收输入
       



        function updateStylesForInsert(insertPosition, insertLength) {
                textData.styles.forEach(style => {
                    // 如果插入位置在当前样式范围之前，或恰好在其开始位置
                    if (insertPosition <= style.start) {
                        style.start += insertLength;
                        style.end += insertLength;
                    } else if (insertPosition < style.end) {
                        // 如果插入位置在当前样式范围内部，只需要调整结束位置
                        style.end += insertLength;
                    }
                    // 如果插入位置在当前样式范围之后，则不需要调整当前样式对象
                });
            }

            function insertTextAtPosition(text, position) {
                // 插入文本并更新textData.text
                const beforeText = textData.text.substring(0, position);
                const afterText = textData.text.substring(position);
                textData.text = beforeText + text + afterText;

                // 更新样式范围
                updateStylesForInsert(position, text.length);

                // 重新渲染文本（这取决于你的渲染逻辑）
                renderText();
            }


            // 根据需要添加更多事件监听器，如compositionstart, compositionupdate

                  // 开始输入复合字符（如中文字符）
            canvas.addEventListener('compositionstart', function(e) {
                isComposing = true;
                console.log('isComposing = true	');
                initialText = textData.text;
                initialSelectionStart = selectionStart; // 假设你已经有这些值
                initialSelectionEnd = selectionEnd;
                            
           
            });

            // 输入复合字符过程中（可选，如果你需要实时获取输入法中的文本，可以使用此事件）
            canvas.addEventListener('compositionupdate', function(e) {
                // e.data 包含当前输入法组合中的文本
                console.log('update	:', e.data);
                clearSelection();
            });

                    // 完成复合字符输入
            canvas.addEventListener('compositionend', function(e) {
                



                const finalText = e.data; // 最终输入的文本
                const deleteLength = initialSelectionEnd - initialSelectionStart; // 如果有选区，计算其长度

                // 如果有需要删除的文本，先处理删除
                if (deleteLength > 0) {
                    updateStylesForDelete(initialSelectionStart, deleteLength);
                    textData.text = textData.text.slice(0, initialSelectionStart) + textData.text.slice(initialSelectionEnd);
                }
                else{
 					// 然后处理文本插入
               		 updateStylesForInsert(initialSelectionStart, finalText.length);
               		 textData.text = textData.text.slice(0, initialSelectionStart) + finalText + textData.text.slice(initialSelectionStart);

                    // 调用insertTextAtPosition插入文本
                    insertTextAtPosition(finalText, cursorPosition);	
                    console.log(cursorPosition);	


                }

               
                renderText(); // 重新渲染文本和样式


                console.log('update	:', e.data);

               // renderText(); // 重新渲染文本
            });


        

            function updateStylesForDelete(position, length) {
                console.log("updateStylesForDelete");
                textData.styles.forEach(style => {
                    if (position < style.start) {
                        style.start -= length;
                        style.end -= length;
                    } else if (position < style.end) {
                        style.end -= length;
                    }

                    // 防止start和end反转
                    if (style.start > style.end) {
                        style.start = style.end;
                    }
                });
            }

     




        // 调整原有的keydown事件监听器，只在非组合输入时处理
        document.addEventListener('keydown', function(e) {
            if (!isComposing && e.key.length === 1) {
                // 处理英文输入等非复合字符输入
            }
            // 其他按键处理，比如Backspace, Delete等
        });


        function isInSelection(x) {
                if (selectionStart === null || selectionEnd === null) return false;
                const startX = charWidths[selectionStart] ? charWidths[selectionStart].x : 0;
                const endX = charWidths[selectionEnd - 1] ? charWidths[selectionEnd - 1].x + charWidths[selectionEnd - 1].width : startX;
                return x >= startX && x <= endX;
            }

       function renderText() {
                let cursorX = 0;
                const cursorY = 100; // Y坐标位置
                ctx.clearRect(0, 0, canvas.width, canvas.height); // 清除之前的渲染

                // 遍历文本之前计算选中文本的最大字体高度
                let maxHeight = 0; 
                if (selectionStart !== null && selectionEnd !== null) {
                    for (let i = selectionStart; i < selectionEnd; i++) {
                        const style = textData.styles.find(s => i >= s.start && i < s.end) || {font: "16px Arial"};
                        const fontSize = parseInt(style.font, 10); // 解析字体大小
                        maxHeight = Math.max(maxHeight, fontSize);
                    }
                    highlightSelection(cursorY - maxHeight, maxHeight + 10); // 调整矩形框高度
                }

                for (let i = 0; i < textData.text.length; i++) {
                    const char = textData.text[i];
                    const style = textData.styles.find(s => i >= s.start && i < s.end) || {font: "16px Arial", color: "black"};
                    ctx.font = style.font;
                    ctx.fillStyle = style.color;

                    const metrics = ctx.measureText(char);
                    ctx.fillText(char, cursorX, cursorY);
                    charWidths[i] = { width: metrics.width, x: cursorX };
                    cursorX += metrics.width;
                }

                if (selectionStart !== null && selectionEnd !== null && selectionStart === selectionEnd - 1) {
                    drawCursor(selectionStart);
                }
                if (cursorPosition !== null && cursorVisible) {
                    drawCursor(cursorPosition);
                }
            }

        function highlightSelection(cursorY, lineHeight) {
            if (selectionStart === null || selectionEnd === null) return;
            const start = charWidths[selectionStart] ? charWidths[selectionStart].x : 0;
            const end = charWidths[selectionEnd - 1] ? charWidths[selectionEnd - 1].x + charWidths[selectionEnd - 1].width : start;
            
            ctx.fillStyle = 'rgba(173, 216, 230, 0.5)'; // 浅蓝色半透明
            ctx.fillRect(start, cursorY, end - start, lineHeight); // 绘制矩形高亮选中区域
        }

        function drawCursor(position) {
                if (cursorVisible && position !== null && charWidths[position]) {
                    const cursorX = charWidths[position].x;
                    const cursorY = 70; // 光标的Y坐标位置，根据需要调整
                    const cursorHeight = 30; // 光标的高度，根据需要调整
                    ctx.fillStyle = 'black'; // 光标颜色
                    ctx.fillRect(cursorX, cursorY, 2, cursorHeight); // 绘制光标
                }
            }

        function clearSelection() {
                // 重置选择状态
                selectionStart = null;
                selectionEnd = null;
                // 如果需要在取消选择时也隐藏光标，取消以下注释
                // cursorPosition = null;
                // cursorVisible = false; // 可选：同时隐藏光标

                // 重新渲染画布以更新显示状态
                renderText();
            }

        
        function blinkCursor() {
                cursorVisible = !cursorVisible;
                renderText();
            }

            setInterval(blinkCursor, 500); // 设置光标闪烁的间隔

            // 事件处理函数与之前相同，但需要更新以管理光标位置和可见性


        function getCharIndex(x) {
            for (let i = 0; i < charWidths.length; i++) {
                const { width, x: charX } = charWidths[i];
                if (x >= charX && x <= charX + width) {
                    return i;
                }
            }
            return null;
        }

        function updateCursorPosition(x) {
            const rect = canvas.getBoundingClientRect();
            const clickX = x - rect.left; // 获取相对于canvas的点击位置
            cursorPosition = getCharIndex(clickX);
            cursorVisible = true; // 显示光标
        }

        canvas.addEventListener('mousedown', function(e) {
                const rect = canvas.getBoundingClientRect();
                const x = e.clientX - rect.left;
                const charIndex = getCharIndex(x);

                if (isInSelection(x)) {
                    // Clicked inside selection, do nothing to keep the selection
                } else if (charIndex !== null) {
                    isSelecting = true;
                    selectionStart = charIndex;
                    selectionEnd = charIndex + 1;
                } else {
                    // selectionStart = null;
                    // selectionEnd = null;
                    // isSelecting = false;                    
                   
                    clearSelection(); // 如果点击非文本区域，则取消选择
  
                    /////////////
                }

                if (hasSelection) {
                    clearSelection(); // 如果已有选择，首先清除
                    hasSelection = false; // 重置选择状态
                }

                // 每次mousedown时重置选择状态，但保存光标位置
                isSelecting = true;
                updateCursorPosition(e.clientX);
                renderText(); // 重新渲染更新画布状态
            });


        canvas.addEventListener('mousemove', function(e) {
            // if (isSelecting) {
            //     const rect = canvas.getBoundingClientRect();
            //     const x = e.clientX - rect.left;
            //     const charIndex = getCharIndex(x);
            //     if (charIndex !== null) {
            //         selectionEnd = charIndex + 1;
            //         renderText(); // 重新渲染以更新选中效果
            //     }
            // }
            if (isSelecting) {
                // 如果在选择过程中移动鼠标，更新选择区域
                const rect = canvas.getBoundingClientRect();
                const x = e.clientX - rect.left;
                const charIndex = getCharIndex(x);
                if (charIndex !== cursorPosition) {
                    selectionStart = Math.min(cursorPosition, charIndex);
                    selectionEnd = Math.max(cursorPosition, charIndex) + 1;
                    renderText();
                }
            }
        });

        canvas.addEventListener('mouseup', function(e) {
            if (isSelecting && selectionStart !== null && selectionEnd !== null && selectionStart !== selectionEnd) {
                    // 如果完成了选择，则保留选择区域
                    cursorVisible = false; // 可以选择隐藏光标
                } else {
                    // 如果没有移动鼠标，则仅显示光标
                    updateCursorPosition(e.clientX);
                }
                isSelecting = false; // 重置选择状态
                renderText(); // 重新渲染以应用最终状态
            // const rect = canvas.getBoundingClientRect();
            // const x = e.clientX - rect.left; // 获取鼠标点击的X坐标（相对于canvas）
            // const charIndex = getCharIndex(x); // 调用getCharIndex函数获取charIndex
            // isSelecting = false;

     

            // if (selectionStart !== null && selectionEnd !== null && selectionStart === selectionEnd - 1) {
            //     // 当仅选中一个字符时，重置选择区域，但保留光标位置
            //     selectionStart = null;
            //     selectionEnd = null;
            //     cursorPosition = charIndex; // 设置光标位置
            //     cursorVisible = true; // 保持光标可见
            // } else if (charIndex !== null) {
            //     // 如果点击位置有效，但不是单字符选择，设置光标并显示
            //     cursorPosition = charIndex;
            //     cursorVisible = true;
            // } else {
            //     // 如果点击的位置不是有效字符位置，清除光标和选择状态
            //     cursorPosition = null;
            //     cursorVisible = false; // 隐藏光标
            //     clearSelection(); // 清除选择区域
            // }

            // renderText(); // 重新渲染画布
        });

        canvas.addEventListener('dblclick', function(e) {
                const rect = canvas.getBoundingClientRect();
                const x = e.clientX - rect.left;
                if (isInSelection(x)) {
                    // Double clicked inside selection
                    const selectedText = textData.text.substring(selectionStart, selectionEnd);
                    console.log(selectedText); // Print the selected text to the console
                }
            });

            document.addEventListener('keydown', function(e) {
                // 确保不是控制键，比如箭头键、Backspace等
                if (e.key.length === 1) {
                    // 在这里插入字符到textData.text
                    const beforeCursor = textData.text.slice(0, cursorPosition);
                    const afterCursor = textData.text.slice(cursorPosition);
                    textData.text = beforeCursor + e.key + afterCursor;

                    // 更新光标位置
                    cursorPosition++;

                    renderText(); // 重新渲染文本
                }

                if (e.key === 'Backspace' && cursorPosition > 0) {
                    const beforeCursor = textData.text.slice(0, cursorPosition - 1);
                    const afterCursor = textData.text.slice(cursorPosition);
                    textData.text = beforeCursor + afterCursor;

                    cursorPosition--;

                    renderText();
                } else if (e.key === 'Delete' && cursorPosition < textData.text.length) {
                    const beforeCursor = textData.text.slice(0, cursorPosition);
                    const afterCursor = textData.text.slice(cursorPosition + 1);
                    textData.text = beforeCursor + afterCursor;

                    // 光标位置不变

                    renderText();
                }

                if (e.key === 'ArrowLeft' && cursorPosition > 0) {
                    cursorPosition--;
                    renderText();
                } else if (e.key === 'ArrowRight' && cursorPosition < textData.text.length) {
                    cursorPosition++;
                    renderText();
                }
            });


        renderText();
    });



    </script>
</body>
</html>
